【EFS】マウントターゲットはフェイルオーバーしない
EFSが東京リージョンで一般利用可能になって2年、実際にEFSサービスを使ってシステムを構築してみたのはここ最近で「EFSで障害が発生した時ってどうなるんだろう?フルマネージドサービスだからフェイルオーバーもよしなにやってくれるんだろう」と思って調べてみました。
構成図
ALB配下にAZ毎にEC2をそれぞれ配置し、それぞれのAZにマウントターゲットを持つEFSリソースを作成しました。ファイルシステムは、マウントターゲットであるENIを経由してアクセスできます。
EC2インスタンスは、Amazon Linux2でEFSマウントヘルパーを使ってマウントしています。
障害発生時(意図的な)の挙動
意図的に障害を発生させてみます。
まずは、セキュリティグループを変更し、tcp/2049にアクセスできない場合です。
書き込みしようとするとアクセスできない為、レスポンスが返ってきません。ターゲットのステータスがUnhealthyとなります。セキュリティグループを元に戻すと数秒でレスポンスが返ってくるようになります。
次にマウントターゲットを削除します。物理的にアクセスできない場合です。
セキュリティグループを変更した時と同様にレスポンスが返ってきません。ターゲットのステータスがUnhealthyとなります。削除前のサブネット、IPアドレス、セキュリティグループでマウントターゲットを再作成すると数分経ってレスポンスが返ってくるようになります。
セキュリティグループの変更、マウントターゲットの削除どちらも共通点は、フェイルオーバーされないことです。マウントターゲットにアクセスできない時は別AZのマウントターゲットを経由するものとイメージしていました。
EFSはフェイルオーバーしない
EFSのFAQにはフェイルオーバーについて、次のように書かれています。
Q:Amazon EFS の設計では高耐久性と高可用性はどのように実現されていますか? 各ファイルシステムオブジェクト (ディレクトリ、ファイルおよびリンクなど) は、複数のアベイラビリティーゾーンに冗長的に保存されます。さらに、ファイルシステムにはそのシステムが存在するリージョン内にあるすべてのアベイラビリティーゾーンから同時にアクセスできます。つまり、同じリージョン内の AZ から別の AZ にフェイルオーバーするようアプリケーションのアーキテクチャを設計することで、アプリケーションに最高度の可用性を確保できます。マウントターゲット自体は、高可用性を確保できるように設計されています。https://aws.amazon.com/jp/efs/faq/#Data_protection_and_availability
つまり、マウントターゲットは、AZ間を跨ぐフェイルオーバー機能はありません。マウントターゲットで障害が発生すると復旧するまでレスポンスが返ってこない状態となります。
フェイルオーバーを実現する場合は、OSレイヤー以上で障害を検知し、フェイルオーバーする仕組みを考える必要があります。
umount→mount
アクセスできないマウントターゲットを強制的にumountし、別AZのマウントターゲットをmountすれば良いと考えました。結論から言うとフェイルオーバーと呼ぶには程遠いものでした。。
ログからマウント状態の障害を検知できるか調査しました。EFSマウントヘルパーは、EFSマウント時のログを記録するmount.log、stunnelプロセスを監視するamazon-efs-mount-watchdogのログが出力されます。が、意図的にアクセスできなくなった状態になってもログは何も出力されません。
https://docs.aws.amazon.com/ja_jp/efs/latest/ug/efs-mount-helper.html#mount-helper-logs
次に/var/log/messagesを確認します。nfsのタイムアウトしたメッセージが出力されています。
Aug 13 17:50:56 hostname kernel: nfs: server 127.0.0.1 not responding, timed out
ただ、意図的にアクセスできない状態になってもすぐには出力されず上記ログが出力されるまで数分かかる為、障害検知できてもフェイルオーバーさせるまでに時間がかかってしまいます。
また、マウントする際の注意点が、EFSマウントヘルパーを使ったマウントです。EFSマウントヘルパーは、DNS名(fs-xxxxxxxx)を使ってマウントする際、EC2インスタンスと同じAZのマウントターゲットのIPアドレスだけ返ってきて異なるAZのマウントターゲットのIPアドレスは返ってきません。(同じAZのAレコードだけ返す仕組みがわからず。。)
$ dig fs-xxxxxxxx.efs.ap-northeast-1.amazonaws.com +short
10.0.100.xxx
異なるAZのマウントターゲットをマウントするには、NFSクライアントを利用し、IPアドレスを指定してマウントを実行する必要があります。
https://docs.aws.amazon.com/ja_jp/efs/latest/ug/mounting-fs-mount-cmd-ip-addr.html
ただ、マウントターゲット毎にIPアドレスが異なる為、AMIでスクリプトを仕込んで利用する場合は、不向きだと考えられます。
ファイルシステムにあるコンテンツを指定したヘルスチェック
フェイルオーバーの実現は厳しいと感じ、AWSサービスで可用性を確保できる構成を考えました。EFSにコンテンツを格納している場合、HealthCheckPathをファイルシステムにあるコンテンツを指定します。EFSにアクセスできない時は、レスポンスが返ってこないのでHealthCheckTimeoutSecondsの設定(デフォルト5秒)に基づき、ターゲットがUnhealthyとなって障害が発生した側は負荷分散されません。復旧するまでシングルAZでサービスを継続することになるので、3AZで冗長できるのであれば高可用性が確保された構成と考えられます。
まとめ
残念ながらフェイルオーバーする仕組みを実現する良い方法は見つけられませんでした。NFSサーバを自前で構築すれば、NFSサーバをクラスター化して、VIPを切り替える仕組みを導入することは可能です。ただ、自前で用意するにはクラスター化したリソース費用、運用、メンテナンスを検討する必要があります。フェイルオーバーという手段にこだわらず、AWSサービスに用意されている機能、で可用性を設計することも選択肢の一つだと考えます。